import dayjs from "dayjs";

import VariableService from "./VariableService";
import ToolbarService from "./ToolbarService";
import { SocketService } from "@/socket/SocketService";
import { SimulationSocketOption } from "@/socket/SimulationSocketOption";
import SceneService from "./SceneService";
import ConsoleService from "./ConsoleService";
import { simHttp } from "@/util/httpClient/SimulationHttpClient";
import { markRaw, reactive } from "vue";
import { API } from "@/constants/api";
import { modelHttp } from "@/util/httpClient/ModelNodeHttpClient";
import BreakPointsService, { BreakpointTriggerType } from "./BreakpointsService";
import SubwinIPCService from "./SubwinIPCService";
import SimApi from "./SimApi";
import ModelApi from "./ModelApi";
import SimStatus from "./SimStatus";
import WinService from "./WinService";
import InstanceTable from "./InstanceTable";
import IpcService from "./IpcService";
import DocxService from "./DocxService";
import Ue4Service from "./Ue4Service";
import LocalFileService from "./LocalFileService";
import { ImageSwitcherService } from "./ImageSwitcher";
import Locale from "./Locale";
import { loading } from "@/common/Loading";
import * as remote from '@electron/remote';
import { formatTime, storage } from '@/util/common';
import { InfoLevel, SimulateStatusType, SimulationConfig } from "./_";
import { SimUI } from "./SimUI";
import { Metaclasses, PropertyKey } from "@/constants/constants";
import { SimFrameToSubChannel } from "./types/Win";
import { ChartsService } from "./charts/ChartsService";
import { getTimerGapByCpus } from "@/util/common";
import { ClockService } from "./ClockService";
import { LogFileService } from "./LogFileService";
import { rmi } from "@/util/RMI";

export class SimulateController {

  simEnginCheckTimes = 1;
  projectId = "";
  mdesignVersion = "";
  modelId = '';
  type = ""; // simulation type
  sessionId = '';
  sceneId = "";
  matlabVersion = "";
  matlabPath = "";
  lang = '';
  designVersion = "";
  simulationConfig = new SimulationConfig()
  customizedTimerGap: number

  socketService: SocketService;
  variableService: VariableService;
  toolbarService: ToolbarService;
  sceneService: SceneService;
  consoleService: ConsoleService;
  breakpointsService: BreakPointsService;
  subwinIpcService: SubwinIPCService;
  instanceTable: InstanceTable;
  ipcService: IpcService;
  simApi: SimApi;
  modelApi: ModelApi;
  winService: WinService;
  locale: Locale;
  simStatus: SimStatus;
  simUIService: SimUI;
  docxService: DocxService;
  ue4Service: Ue4Service;
  imageSwitcherService: ImageSwitcherService;
  chartsService: ChartsService;
  localFileService: LocalFileService;
  clockService: ClockService;
  logFileService: LogFileService;

  private static _instance: any;

  public static getInstance(): SimulateController {
    if (!this._instance) {
      this._instance = reactive(new SimulateController());
    }
    return this._instance;
  }

  private constructor() {

    this.customizedTimerGap = getTimerGapByCpus();
    this.lang = storage.get('locale') || 'zhCn';
    this.subwinIpcService = new SubwinIPCService();
    this.socketService = new SocketService(new SimulationSocketOption());

    this.logFileService = markRaw(new LogFileService(this));
    this.clockService = new ClockService(this);
    this.variableService = new VariableService(this);
    this.toolbarService = new ToolbarService(this);
    this.sceneService = new SceneService(this);
    this.consoleService = new ConsoleService(this);
    this.breakpointsService = new BreakPointsService(this);
    this.instanceTable = new InstanceTable(this);
    this.ipcService = new IpcService(this);
    this.docxService = new DocxService(this);
    this.simApi = new SimApi(this);
    this.modelApi = new ModelApi();
    this.winService = new WinService(this);
    this.locale = new Locale();
    this.simStatus = new SimStatus(this);
    this.simUIService = new SimUI(this);
    this.ue4Service = new Ue4Service(this);
    this.imageSwitcherService = new ImageSwitcherService(this);
    this.chartsService = new ChartsService(this);
    this.localFileService = new LocalFileService(this);

    this.initDesignVersion();
    this.initMatlabVersion();
  }

  serviceInit() {
    this.logFileService.serviceInit();
    this.clockService.serviceInit();
    this.consoleService.serviceInit();
    this.variableService.serviceInit();
    this.sceneService.serviceInit();
    this.toolbarService.serviceInit();
    this.simStatus.serviceInit();
    this.winService.serviceInit();
    this.chartsService.serviceInit();
    this.simEnginCheckTimes = 1;
  }

  async initDesignVersion() {
    this.designVersion = await rmi.query('app.version') as string;
  }

  async initMatlabVersion(p?: { version: string, path: string }) {
    if (p) {
      this.matlabVersion = p.version;
      this.matlabPath = p.path;
    } else {
      if (!this.matlabVersion) {
        const { data } = await this.modelApi.systemMatlabConfig();
        this.matlabVersion = data.data.matlabVersion;
        this.matlabPath = data.data.matlabPath;
      }
    }
  }

  async checkSimserviceStarted(cb?: () => Promise<any>) {

    if (this.simEnginCheckTimes === 1) {
      loading.popup(this.locale.localeTrans('ins_wait'), {
        msg: this.locale.localeTrans('cancel'),
        callback: () => {
          const win: Electron.BrowserWindow = remote.BrowserWindow.getFocusedWindow() as Electron.BrowserWindow;
          win.close();
        }
      });
    }

    try {
      await this.simApi.hello({ timeout: 1200 });
      this.winService.recordQueryParams();
      // add mdesign version
      if (this.projectId) {
        const { version } = (await this.modelApi.getProjectInfo(this.projectId)).data?.data;
        this.mdesignVersion = version;
        console.log("mdesign version: ", this.mdesignVersion);
      }
      await this.simApi.simulationUpdateMatlabVersion({ path: this.matlabPath, version: this.matlabVersion });
      if (cb) {
        await cb();
      }
    } catch (error) {
      if (this.simEnginCheckTimes % 5 === 1) {
        this.modelApi.pluginSiminstall();
      }
      setTimeout(async () => {
        await this.checkSimserviceStarted(cb);
      }, 2000);
    }
    this.simEnginCheckTimes++;
  }

  async autoLoadModelData() {
    this.toolbarService.status = SimulateStatusType.initing;
    let modelIds = Array.isArray(this.modelId)
      ? [...this.modelId]
      : [this.modelId];
    console.info('auto load prams: ', {
      projectId: this.projectId,
      modelIds,
    });
    const { data } = await this.simApi.simulationLoad({
      projectId: this.projectId,
      modelIds,
    });
    console.info('auto load modelIds: ', modelIds);
    console.info('auto load result: ', data);
    if (data.code !== 1000) {
      this.consoleService.addRecord({
        message: this.locale.localeTrans("ini_error"),
        level: InfoLevel.ERROR
      });
    } else {
      controller.sessionId = data.data;
      controller.socketService?.sendJSON({
        api: "api_session_bind",
        data: {
          simulationSessionId: controller.sessionId
        }
      });
    }
    this.toolbarService.status = SimulateStatusType.none;
  }

  async simEnginInit() {
    this.toolbarService.lastEndTime = 0;
    this.toolbarService.lastEndTimeStamp = Date.now();
    this.toolbarService.lastUpdateTime = this.toolbarService.lastEndTimeStamp;

    // 初始化仿真时钟配置
    await this.clockService.initClockConfig();
    // 清空日志
    this.logFileService.clear();
    this.simulationConfig.currentNodeIsSimulationConfig = false;

    if (!Array.isArray(this.modelId)) {
      // is start form SimConfig-Model and deal
      const modelInfoRes = await this.modelApi.modelInfo({ projectId: this.projectId, modelId: this.modelId });
      if (modelInfoRes?.data.data) {
        if (modelInfoRes.data.data?.baseSt === Metaclasses.SimulationConfig) {
          this.simulationConfig.currentNodeIsSimulationConfig = true;
          const keyValuesRes = await this.modelApi.modelPropertyGetPropertyValuesById({
            projectId: this.projectId,
            modelId: this.modelId,
            propertyKeys: [
              PropertyKey.SimulationConfig_ClockMode,
              PropertyKey.SimulationConfig_IsRelativeTime,
              PropertyKey.SimulationConfig_ClockRatio,
              PropertyKey.SimulationConfig_StartTime,
              PropertyKey.SimulationConfig_autoStart,
              PropertyKey.SimulationConfig_silent,
              PropertyKey.SimulationConfig_autostartActiveObjects,
              PropertyKey.SimulationConfig_animationSpeed,
            ]
          });
          const res = keyValuesRes.data?.data;
          if (res) {
            // 默认实时仿真时钟模式
            this.simulationConfig.clockMode = res[PropertyKey.SimulationConfig_ClockMode]?.value === 'SimulationClockKind-internalSimulationClock' ? 'Internal' : 'RealTime';
            // 默认仿真时钟绝对时间模式
            const isRelativeTime = res[PropertyKey.SimulationConfig_IsRelativeTime]?.value;
            this.simulationConfig.isRelativeTime = typeof isRelativeTime !== 'boolean' ? true : isRelativeTime;
            this.simulationConfig.clockRatio = res[PropertyKey.SimulationConfig_ClockRatio]?.value || 1;
            this.simulationConfig.startTime = res[PropertyKey.SimulationConfig_StartTime]?.value;
            this.simulationConfig.autoStart = res[PropertyKey.SimulationConfig_autoStart]?.value;
            this.simulationConfig.autostartActiveObjects = res[PropertyKey.SimulationConfig_autostartActiveObjects]?.value;
            this.simulationConfig.silent = res[PropertyKey.SimulationConfig_silent]?.value;
            // if (res[PropertyKey.SimulationConfig_animationSpeed].value !== 95) {
            this.toolbarService.speed = res[PropertyKey.SimulationConfig_animationSpeed]?.value;
            // }
          }
          // sim-clock
          const simClockRelateRes = (await this.modelApi.modelPropertyListByKeys({
            projectId: this.projectId,
            modelId: this.modelId,
            keys: [
              PropertyKey.SimulationConfig_ClockRatio,
              PropertyKey.SimulationConfig_StartTime,
              PropertyKey.SimulationConfig_StepSize,
            ]
          })).data?.data;
          if (simClockRelateRes) {
            for (const scd of simClockRelateRes) {
              switch (scd.key) {
                case PropertyKey.SimulationConfig_StartTime:
                  this.chartsService.simStartTime = isNaN(Number(scd.value)) ? null : scd.value;
                  break;
                case PropertyKey.SimulationConfig_ClockRatio:
                  this.chartsService.simClockRatio = scd.value || 1;
                  break;
                case PropertyKey.SimulationConfig_StepSize:
                  this.chartsService.simStepSize = scd.value || 1000;
                  break;
                default:
              }
            }
          }
        } else {
        // deal m-design sim-config
          const simulationMdesignConfigRes = await this.modelApi.projectGetSimulationConfig(this.projectId);
          if (simulationMdesignConfigRes.data?.data) {
            controller.simulationConfig = { ...simulationMdesignConfigRes.data?.data };
            // 非仿真配置节点仿真时，默认执行实时相对的仿真时钟
            this.simulationConfig.clockMode = 'RealTime';
            this.simulationConfig.isRelativeTime = true;
          }
        }
      } else {
        // 还原仿真配置
        this.simulationConfig = new SimulationConfig();
      }
      // this.toolbarService.statusChange = SimulateStatusType.ready;
    }

    // execute sim-config
    this.toolbarService.handleSpeedChange(this.toolbarService.speed);
    this.simApi.simulationConfig({
      ...this.simulationConfig,
      speed: this.toolbarService.speed,
      simulationSessionId: this.sessionId,
      designVersion: this.designVersion,
    });

    if (['TCommon', 'TContext', 'TSCommon'].includes(this.type)) {
      await this.instanceTable.sim();
    } else {
      await this.simApi.simulationInit({
        modelId: this.modelId,
        simulationSessionId: this.sessionId,
        type: this.type,
      });
    }

    // 先判断是否配置了仿真UI
    const prop = await this.modelApi.modelPropertyByKey({ projectId: this.projectId, modelId: this.modelId, key: PropertyKey.SimulationConfig_UI });
    console.log(prop);
    if (!prop || !prop.value || !prop.value[0]) {
      await controller.winService.dealFramesData();
      this.afterDealFramesData();
    } else {
      // 存在仿真UI时，后续逻辑交由 api_display_simulate_ui 触发
    }
    
  }

  afterDealFramesData() {
    // get scene data
    this.sceneService.sceneVariableDataInit();

    // send this status to sub-win
    this.winService.sendInfoToSubWins({
      type: SimFrameToSubChannel.SimInit,
    });

    if (!Array.isArray(this.modelId)) {
      this.toolbarService.statusChange = SimulateStatusType.ready;
    }
  }

  guard(data: { guardText: any; executionId: any; edgeId: any; }) {
    app.$confirm.popup({
      title: this.locale.localeTrans('judge'),
      okText: this.locale.localeTrans('yes'),
      cancelText: this.locale.localeTrans('no'),
      content: data.guardText,
      mask: false,
      showClose: false,
      onClickOk: (_this: { visible: boolean; $emit: (arg0: string) => void; }) => {
        simHttp.get({
          api: "simulation/guard",
          data: {
            guard: true,
            executionId: data.executionId,
            simulationSessionId: this.sessionId,
            edgeId: data.edgeId,
          }
        });
        _this.visible = false;
        _this.$emit("sure");
      },
      onClickCancel: (_this: { visible: boolean; $emit: (arg0: string) => void; }) => {
        simHttp.get({
          api: "simulation/guard",
          data: {
            guard: false,
            executionId: data.executionId,
            simulationSessionId: this.sessionId,
            edgeId: data.edgeId,
          }
        });
        _this.visible = false;
        _this.$emit("sure");
      }
    });
  }

  getBreakpoints() {
    return modelHttp.get({
      api: API.GET_BREAKPOINT,
      data: {
        projectId: this.projectId
      }
    });
  }

  addBreakpoints(modelId: string) {
    return modelHttp.post({
      api: API.ADD_BREAKPOINT,
      data: {
        projectId: this.projectId,
        modelId: modelId
      }
    });
  }

  deleteBreakpoints(modelIds: string[]) {
    return modelHttp.post({
      api: API.DELETE_BREAKPOINT,
      data: {
        projectId: this.projectId,
        modelIds: modelIds
      }
    });
  }

  deleteAllBreakpoints() {
    return modelHttp.post({
      api: API.DELETE_ALL_BREAKPOINT,
      data: {
        projectId: this.projectId
      }
    });
  }

  editBreakpoint(modelId: string, editField: "condition" | "enabled" | "suspend", value: string | boolean | BreakpointTriggerType) {
    return modelHttp.post({
      api: API.EDIT_BREAKPOINT,
      data: {
        projectId: this.projectId,
        modelId: modelId,
        editField: editField,
        value: value,
      }
    });
  }

  notifySimBackendBreakpoints() {
    return simHttp.get({
      api: API.NOTIFY_BREAKPOINTS_CHANGED,
      data: {
        projectId: this.projectId,
        simulationSessionId: this.sessionId
      }
    });

  }

  queryModelInfo(modelId: string) {
    return modelHttp.get({
      api: API.QUERY_MODEL_INFO,
      data: {
        projectId: this.projectId,
        modelId: modelId
      }
    });
  }

  queryModelInfos(modelIds: string[]) {
    return modelHttp.post({
      api: API.QUERY_MODEL_INFOS,
      data: {
        projectId: this.projectId,
        modelIds: modelIds
      }
    });
  }
}

export const controller = SimulateController.getInstance();
